﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace Mahanet.Abyss.PathCalculation
{
	/// <summary>
	/// Interaction logic for MainWindow.xaml
	/// </summary>
	public partial class MainWindow : Window, IDataDisplayer
	{
		#region Consts

		private const int NUMBER_OF_DATA_POINTS_GENERATED = 8;

		#endregion

		#region Fields

		private Coordinate2DDataSourceMocker m_DataMocker;

		private Coordinate2DOracleFactory m_ApproximatorFactory;

		private Line m_DangerZone;
		private Line m_GunAngle;

		private Dictionary<string, List<UIElement>> m_ElementGroups = new Dictionary<string, List<UIElement>>();

		#endregion

		#region C'tor

		public MainWindow()
		{
			InitializeComponent();

			m_ApproximatorFactory = new Coordinate2DOracleFactory();

			Approximator.ItemsSource = Coordinate2DOracleFactory.AllFactories.Keys;
			Approximator.SelectedIndex = 0;

			m_DataMocker = new Coordinate2DDataSourceMocker(this, m_ApproximatorFactory);
		}

		#endregion

		#region IDataDisplayer

		public void AddDataPoint(string dataGroupName, double x, double y)
		{
			AddDataPoint(dataGroupName, x, y, Colors.Red, 5);
		}

		public void AddFunction(string dataGroupName, FunctionApproximation<double, Coordinate2D> function)
		{
			AddFunction(dataGroupName, function, Colors.Blue, 2);
		}

		public void AddDataPoint(string dataGroupName, double x, double y, Color pointColor, int size)
		{
			if (!this.Dispatcher.CheckAccess())
			{
				this.Dispatcher.Invoke(new Action(() => AddDataPoint(dataGroupName, x, y, pointColor, size)));

				return;
			}

			if (pointColor == null)
			{
				pointColor = Colors.Red;
			}

			int width = size;
			int height = size;
			Ellipse ellipse = new Ellipse();
			ellipse.Fill = new SolidColorBrush(pointColor);
			ellipse.Width = width;
			ellipse.Height = height;

			ellipse.SetValue(Canvas.LeftProperty, x - width / 2);
			ellipse.SetValue(Canvas.TopProperty, this.MainCanvas.ActualHeight - y - height / 2);


			if (!m_ElementGroups.ContainsKey(dataGroupName))
			{
				m_ElementGroups.Add(dataGroupName, new List<UIElement>());
			}

			m_ElementGroups[dataGroupName].Add(ellipse);

			this.MainCanvas.Children.Add(ellipse);
		}

		public void AddFunction(string dataGroupName, FunctionApproximation<double, Coordinate2D> approx, Color functionColor, int size)
		{
			if (!this.Dispatcher.CheckAccess())
			{
				this.Dispatcher.Invoke(new Action(() => AddFunction(dataGroupName, approx, functionColor, size)));

				return;
			}

			if (functionColor == null)
			{
				functionColor = Colors.Blue;
			}

			for (int i = 0; i < this.MainCanvas.ActualWidth; i++)
			{
				Coordinate2D point = approx(i);
				AddDataPoint(dataGroupName, point.X, point.Y, functionColor, size);
			}
		}

		public void ClearPoints()
		{
			if (!this.Dispatcher.CheckAccess())
			{
				this.Dispatcher.Invoke(new Action(() => ClearPoints()));

				return;
			}
			this.MainCanvas.Children.Clear();
			if (m_DangerZone != null)
			{
				this.MainCanvas.Children.Add(m_DangerZone);
			}

			if (m_GunAngle != null)
			{
				this.MainCanvas.Children.Add(m_GunAngle);
			}
		}

		public void ClearPoints(string dataGroupName)
		{
			if (!this.Dispatcher.CheckAccess())
			{
				this.Dispatcher.Invoke(new Action(() => ClearPoints(dataGroupName)));

				return;
			}
			if (!m_ElementGroups.ContainsKey(dataGroupName))
			{
				return;
			}

			foreach (UIElement element in m_ElementGroups[dataGroupName])
			{
				this.MainCanvas.Children.Remove(element);
			}

			m_ElementGroups[dataGroupName].Clear();
		}

		public void DisplayStatus(string s)
		{
			if (!this.Dispatcher.CheckAccess())
			{
				this.Dispatcher.Invoke(new Action(() => DisplayStatus(s)));

				return;
			}

			this.Result.Text = s;
		}

		public void MarkErrorInPoint(Coordinate2D realValue, Coordinate2D approximation, int numberOfPointsGiven, int targetPoint)
		{
			if (!this.Dispatcher.CheckAccess())
			{
				this.Dispatcher.Invoke(new Action(() => MarkErrorInPoint(realValue, approximation, numberOfPointsGiven, targetPoint)));

				return;
			}

			MoreInfo.Text = string.Format("With {0} data points: Real value {1}; approximation {2} (on target point {3}).\n"
											+ "Difference is {4} units.",
											numberOfPointsGiven,
											realValue.ToString(),
											approximation.ToString(),
											targetPoint,
											realValue.DistanceTo(approximation));
		}

		public void MarkDangerZone(Segment2D dangerZone)
		{
			if (!this.Dispatcher.CheckAccess())
			{
				this.Dispatcher.Invoke(new Action(() => MarkDangerZone(dangerZone)));

				return;
			}

			this.MainCanvas.Children.Remove(m_DangerZone);

			m_DangerZone = new Line();
			m_DangerZone.X1 = dangerZone.StartPoint.X;
			m_DangerZone.X2 = dangerZone.EndPoint.X;
			m_DangerZone.Y1 = this.MainCanvas.ActualHeight - dangerZone.StartPoint.Y;
			m_DangerZone.Y2 = this.MainCanvas.ActualHeight - dangerZone.EndPoint.Y;

			m_DangerZone.Stroke = new SolidColorBrush(Colors.Orange);
			m_DangerZone.StrokeThickness = 5;
			m_DangerZone.Visibility = Visibility.Visible;

			m_DangerZone.SnapsToDevicePixels = true;
			m_DangerZone.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);

			this.MainCanvas.Children.Add(m_DangerZone);
		}

		public void AddGunAngle(Coordinate2D center, Vector2D direction)
		{
			if (!this.Dispatcher.CheckAccess())
			{
				this.Dispatcher.Invoke(new Action(() => AddGunAngle(center, direction)));

				return;
			}

			this.MainCanvas.Children.Remove(m_GunAngle);

			m_GunAngle = new Line();
			m_GunAngle.X1 = center.X;
			m_GunAngle.X2 = (center + direction.Normalized()*10).X;
			m_GunAngle.Y1 = this.MainCanvas.ActualHeight - center.Y;
			m_GunAngle.Y2 = this.MainCanvas.ActualHeight - (center + direction.Normalized() * 10).Y;

			m_GunAngle.Stroke = new SolidColorBrush(Colors.SlateBlue);
			m_GunAngle.StrokeThickness = 2;
			m_GunAngle.Visibility = Visibility.Visible;

			m_GunAngle.SnapsToDevicePixels = true;
			m_GunAngle.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);

			this.MainCanvas.Children.Add(m_GunAngle);
		}

		#endregion

		#region Event Handlers

		private void MainCanvas_MouseDown(object sender, MouseButtonEventArgs e)
		{
			Point mousePosition = Mouse.GetPosition(this.MainCanvas);

			double dataPointX = mousePosition.X;
			double dataPointY = this.MainCanvas.ActualHeight - mousePosition.Y;

			AddDataPoint("Clicked", dataPointX, dataPointY);
		}

		private void GenerateRandomPoints_Click(object sender, RoutedEventArgs e)
		{
			int numberOfPoints;
			int animationLengthMs;
			int tickLengthMs;
			double sampleError;
			if (Int32.TryParse(this.NumberOfPoints.Text, out numberOfPoints) &&
				numberOfPoints > 0 &&
				Int32.TryParse(this.AnimationLengthMs.Text, out animationLengthMs) &&
				animationLengthMs > 0 &&
				Int32.TryParse(this.TickLengthMs.Text, out tickLengthMs) &&
				tickLengthMs > 0 &&
				Double.TryParse(this.SampleError.Text, out sampleError) &&
				sampleError >= 0 && sampleError < 1)
			{
				m_DataMocker.GenerateAndDisplayPoints(this.MainCanvas.ActualWidth, this.MainCanvas.ActualHeight, numberOfPoints,
													  animationLengthMs, tickLengthMs, sampleError);
			}
		}

		private void GenerateDangerZone_Click(object sender, RoutedEventArgs e)
		{
			m_DataMocker.GenerateDangerZone(this.MainCanvas.ActualWidth, this.MainCanvas.ActualHeight);
		}

		private void GenerateStatistics_Click(object sender, RoutedEventArgs e)
		{
			int numberOfPoints;
			int animationLengthMs;
			int tickLengthMs;
			double sampleError;
			if (Int32.TryParse(this.NumberOfPoints.Text, out numberOfPoints) &&
				numberOfPoints > 0 &&
				Int32.TryParse(this.AnimationLengthMs.Text, out animationLengthMs) &&
				animationLengthMs > 0 &&
				Int32.TryParse(this.TickLengthMs.Text, out tickLengthMs) &&
				tickLengthMs > 0 &&
				Double.TryParse(this.SampleError.Text, out sampleError) &&
				sampleError >= 0 && sampleError < 1)
			{
				m_DataMocker.GenerateStatisticData(this.MainCanvas.ActualWidth, this.MainCanvas.ActualHeight, numberOfPoints,
													  animationLengthMs, tickLengthMs, sampleError);
			}
		}

		private void Approximator_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			m_ApproximatorFactory.SelectedFactory = Approximator.SelectedValue.ToString();
		}

		#endregion
	}
}
